package com.ikingtech.framework.sdk.authenticate.operation;

import com.ikingtech.framework.sdk.cache.constants.CacheConstants;
import com.ikingtech.framework.sdk.context.constant.CommonConstants;
import com.ikingtech.framework.sdk.context.constant.SecurityConstants;
import com.ikingtech.framework.sdk.context.security.Identity;
import com.ikingtech.framework.sdk.enums.domain.DomainEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserCategoryEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserConfigTypeEnum;
import com.ikingtech.framework.sdk.enums.system.variable.TokenDelayTypeEnum;
import com.ikingtech.framework.sdk.enums.system.variable.VariableEnum;
import com.ikingtech.framework.sdk.utils.Tools;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.util.MultiValueMap;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.ikingtech.framework.sdk.context.constant.SecurityConstants.*;

/**
 * @author tie yan
 */
@RequiredArgsConstructor
public class IdentityValidator {

    private final StringRedisTemplate redisTemplate;

    private static final List<String> SUPPORT_TOKEN_HEADER_NAMES = List.of(
            "Authorization",
            "Token",
            "X-Access-Token"
    );

    private static final List<String> SUPPORT_TOKEN_QUERY_PARAM_NAMES = List.of(
            "token"
    );

    private static final List<String> SUPPORT_TENANT_CODE_HEADER_NAMES = List.of(
            HEADER_TENANT_CODE
    );

    private static final List<String> SUPPORT_TENANT_CODE_QUERY_PARAM_NAMES = List.of(
            "tenant"
    );

    private static final List<String> SUPPORT_DOMAIN_CODE_HEADER_NAMES = List.of(
            HEADER_DOMAIN_CODE
    );

    private static final List<String> SUPPORT_DOMAIN_CODE_QUERY_PARAM_NAMES = List.of(
            "domain"
    );

    private static final List<String> SUPPORT_APP_CODE_HEADER_NAMES = List.of(
            HEADER_APP_CODE
    );

    private static final List<String> SUPPORT_APP_CODE_QUERY_PARAM_NAMES = List.of(
            "app"
    );

    private static final List<String> SUPPORT_MENU_ID_QUERY_PARAM_NAMES = List.of(
            "menuId"
    );

    private static final List<String> SUPPORT_MENU_ID_HEADER_NAMES = List.of(
            HEADER_MENU_ID
    );

    /**
     * 白名单
     */
    private static final List<String> DEFAULT_IGNORE_PATHS = List.of(

            // auth server
            "/sign",

            // spring boot admin endpoint
            "/metrics/**",
            "/actuator/**",
            "/instances/**",
            "/applications",

            // swagger boot admin endpoint
            "/v2/api-docs/**",
            "/v3/api-docs/**",
            "/swagger-resources/configuration/ui",
            "/swagger-resources",
            "/swagger-resources/configuration/security",
            "/swagger-ui.html",
            "/doc.html",

            // static resource
            "/",
            "/css/*.**.css",
            "/css/**.css",
            "/assets/*.**.css",
            "/assets/**.css",
            "/**/css/*.**.css",
            "/**/css/**.css",
            "/**/assets/*.**.css",
            "/**/assets/**.css",
            "/**/*.**.css",
            "/**/**.css",
            "/js/*.**.js",
            "/js/**.js",
            "/assets/*.**.js",
            "/assets/**.js",
            "/**/js/*.**.js",
            "/**/js/**.js",
            "/**/assets/*.**.js",
            "/**/assets/**.js",
            "/**/*.**.js",
            "/**/**.js",
            "/images/**",
            "/webjars/**",
            "/**/**.ico",
            "/**/**.png",
            "/**/**.jpg",
            "/**/**.svg",
            "/**/**.woff2",
            "/index",
            "/**/index",
            "/**/index.html",

            // druid monitor endpoint
            "/druid/**",

            // oss
            "/oss/**",

            // 页面灰度值
            "/front-gray-scale/value",

            // magic-api
            "/sql-api/**",
            "/config-js",
            "/sql-api/web/**",

            // 微信获取UnionId
            "/wechat/mini/exchange-code",

            // 微信获取手机号
            "/wechat/mini/phone",

            // 绑定用户微信UnionId
            "/system/user/bind/wechat/wechat-union-id",

            // 绑定用户微信UnionId
            "/system/user/bind/wechat/wechat-open-id",

            // 绑定用户微信UnionId
            "/system/user/bind/social",

            // 支付回调
            "/pay/notify/**"
    );

    public static List<String> getDefaultIgnorePaths() {
        return DEFAULT_IGNORE_PATHS;
    }

    public Identity validate(MultiValueMap<String, String> queryParamMap, HttpHeaders headers) {
        String token = this.getFromHeaderOrQueryParam(queryParamMap, headers, SUPPORT_TOKEN_QUERY_PARAM_NAMES, SUPPORT_TOKEN_HEADER_NAMES);

        Identity identity;
        String accessTokenKey = CacheConstants.accessTokenKeyFormatWithoutPrefix(token);
        if (Tools.Str.isBlank(accessTokenKey)) {
            return null;
        }
        String identityStr = this.redisTemplate.opsForValue().get(accessTokenKey);
        if (token.startsWith(SecurityConstants.GLOBAL_TOKEN)) {
            identity = Identity.admin();
        } else {
            identity = Tools.Str.isBlank(identityStr) ? null : Tools.Json.toBean(identityStr, Identity.class);
        }
        if (null == identity) {
            return null;
        }

        // 当前域
        String domainCode = this.getFromHeaderOrQueryParam(queryParamMap, headers, SUPPORT_DOMAIN_CODE_QUERY_PARAM_NAMES, SUPPORT_DOMAIN_CODE_HEADER_NAMES);
        if (Tools.Str.isBlank(domainCode)) {
            if (Tools.Coll.contains(identity.getCategoryCodes(), UserCategoryEnum.PLATFORM_ADMINISTRATOR.name())) {
                identity.setDomainCode(DomainEnum.PLATFORM.name());
            } else {
                identity.setDomainCode(DomainEnum.TENANT.name());
            }
        } else {
            identity.setDomainCode(domainCode);
        }

        String languageKey = domainCode;

        //租户code
        String tenantCode = this.getFromHeaderOrQueryParam(queryParamMap, headers, SUPPORT_TENANT_CODE_QUERY_PARAM_NAMES, SUPPORT_TENANT_CODE_HEADER_NAMES);
        identity.setTenantCode(tenantCode);
        if (Tools.Str.isNotBlank(tenantCode)) {
            languageKey = tenantCode;
        }

        // 当前应用
        identity.setAppCode(this.getFromHeaderOrQueryParam(queryParamMap, headers, SUPPORT_APP_CODE_QUERY_PARAM_NAMES, SUPPORT_APP_CODE_HEADER_NAMES));

        //语言环境
        String language = headers.getFirst(SecurityConstants.HEADER_ENV_LANGUAGE);
        if (Tools.Str.isBlank(language)) {
            language = (String) this.redisTemplate.opsForHash().get(CacheConstants.userConfigFormat(identity.getId(), languageKey), UserConfigTypeEnum.LANGUAGE.name());
        }
        identity.setLang(Tools.Str.isBlank(language) ? CommonConstants.ENV_DEFAULT_LANG : language);

        String menuId = this.getFromHeaderOrQueryParam(queryParamMap, headers, SUPPORT_MENU_ID_QUERY_PARAM_NAMES, SUPPORT_MENU_ID_HEADER_NAMES);
        identity.setMenuId(menuId);
        if (Tools.Str.isBlank(menuId)) {
            List<Object> dataScopeCodeStr = this.redisTemplate.opsForHash().values(CacheConstants.userAuthDetailsFormat(identity.getUsername(), tenantCode));
            if (Tools.Coll.isBlank(dataScopeCodeStr)) {
                identity.setDataScopeCodes(new ArrayList<>());
            } else {
                identity.setDataScopeCodes(Tools.Str.split((String) dataScopeCodeStr.get(0)));
            }
        } else {
            identity.setDataScopeCodes(Tools.Str.split((String) this.redisTemplate.opsForHash().get(CacheConstants.userAuthDetailsFormat(identity.getUsername(), tenantCode), menuId)));
        }

        //token续期
        this.refreshTokenExpireTime(accessTokenKey, identity.getId(), identity.getEndpoint(), tenantCode);
        return identity;
    }

    public String cacheValidatedIdentity(Identity identity) {
        String requestId = Tools.Id.uuid();
        this.redisTemplate.opsForValue().set(CacheConstants.validatedIdentityFormat(requestId), Tools.Json.toJsonStr(identity), 5, TimeUnit.MINUTES);
        return requestId;
    }

    public String getValidatedIdentity(String requestId) {
        if ("DEFAULT_USER".equals(requestId)) {
            return Tools.Json.toJsonStr(Identity.defaultUser());
        }
        return this.redisTemplate.opsForValue().get(CacheConstants.validatedIdentityFormat(requestId));
    }

    private void refreshTokenExpireTime(String accessTokenFormat, String id, String endpoint, String tenantCode) {
        // 从redis中获取token续期类型配置
        String tokenDelayType = (String) this.redisTemplate.opsForHash().get(CacheConstants.systemVariableFormat(tenantCode), VariableEnum.TOKEN_DELAY_TYPE.name());
        if (TokenDelayTypeEnum.NO_DELAY.name().equals(VariableEnum.resolveTokenDelayType(tokenDelayType))) {
            // 如果不自动续期，直接返回
            return;
        }
        // 如果自动续期，从redis中获取token过期时间配置
        long expire = VariableEnum.resolveTokenExpire(this.redisTemplate.opsForHash().get(CacheConstants.systemVariableFormat(tenantCode), VariableEnum.TOKEN_EXPIRE.name()));
        String loginUserFormat = CacheConstants.loginUserFormat(id, endpoint);
        this.redisTemplate.expire(accessTokenFormat, expire, TimeUnit.MINUTES);
        this.redisTemplate.expire(loginUserFormat, expire, TimeUnit.MINUTES);
    }

    private String getFromHeaderOrQueryParam(MultiValueMap<String, String> queryParamMap, HttpHeaders headers, List<String> queryParamNames, List<String> headerNames) {
        String result = null;
        for (String queryParamName : queryParamNames) {
            result = queryParamMap.getFirst(queryParamName);
            if (Tools.Str.isNotBlank(result)) {
                break;
            }
        }
        if (Tools.Str.isBlank(result)) {
            for (String headerName : headerNames) {
                result = headers.getFirst(headerName);
                if (Tools.Str.isNotBlank(result)) {
                    break;
                }
            }
        }
        return result;
    }
}
